#include "serialport_plugin.h"
#include "serialport_mgr.h"

#include <QDebug>
#include <QDataStream>
#include "share_function.h"

#define DF_NOTIFY_HEARTBEAT         "heartBeat"
#define DF_NOTIFY_FLUOR_DATA        "fl_data"

/*
 * Function:    SerialportPlugin
 * Description: default constructor
 * Paramer:     QObject *parent - the point of parent
 * Return:      none
 */
SerialportPlugin::SerialportPlugin(QObject *parent)
{
    qDebug() << Q_FUNC_INFO;
    m_pParent = parent;

    Q_ASSERT(SerialportMgr::getInstance(this));
}

/*
 * Function:    openSerialport
 * Description: open a serial port
 * Paramer:     void
 * Return:      int - the error code
 */
int SerialportPlugin::openSerialport()
{
    qDebug() << Q_FUNC_INFO;

    // Signal-slot initialization
    std::vector<bool> vecConnect;
    vecConnect.push_back(QObject::connect(this, SIGNAL(signalBuildACK(const ST_MIN_PKG_INFO &)), this, SLOT(slotBuildACK(const ST_MIN_PKG_INFO &)), static_cast<Qt::ConnectionType>(Qt::DirectConnection | Qt::UniqueConnection)));
    vecConnect.push_back(QObject::connect(this, SIGNAL(signalReplyACK(QByteArray, bool)), SerialportMgr::getInstance(this), SLOT(slotReplyACK(QByteArray, bool)), static_cast<Qt::ConnectionType>(Qt::QueuedConnection | Qt::UniqueConnection)));
    vecConnect.push_back(QObject::connect(this, SIGNAL(signalPayloadFetched(const QByteArray &)), SerialportMgr::getInstance(this), SLOT(slotPayloadFetched(const QByteArray &)), static_cast<Qt::ConnectionType>(Qt::QueuedConnection | Qt::UniqueConnection)));
    vecConnect.push_back(QObject::connect(SerialportMgr::getInstance(this), SIGNAL(signalDataReceived(const QByteArray &)), this, SLOT(slotDataReceived(const QByteArray &)), static_cast<Qt::ConnectionType>(Qt::QueuedConnection | Qt::UniqueConnection)));
    for(int i=0; i<(int)vecConnect.size(); i++) {
      Q_ASSERT(vecConnect.at(i));
    }

    return SerialportMgr::getInstance(this)->openCOM();
}

/*
 * Function:    closeSerialport
 * Description: close a serial port
 * Paramer:     void
 * Return:      void
 */
void SerialportPlugin::closeSerialport()
{
    qDebug() << Q_FUNC_INFO;

    QObject::disconnect(this, SIGNAL(signalBuildACK(const ST_MIN_PKG_INFO &)), this, SLOT(slotBuildACK(const ST_MIN_PKG_INFO &)));
    QObject::disconnect(this, SIGNAL(signalReplyACK(QByteArray, bool)), SerialportMgr::getInstance(this), SLOT(slotReplyACK(QByteArray, bool)));
    QObject::disconnect(this, SIGNAL(signalPayloadFetched(const QByteArray &)), SerialportMgr::getInstance(this), SLOT(slotPayloadFetched(const QByteArray &)));
    QObject::disconnect(SerialportMgr::getInstance(this), SIGNAL(signalDataReceived(const QByteArray &)), this, SLOT(slotDataReceived(const QByteArray &)));

    SerialportMgr::getInstance(this)->closeCOM();
}

/*
 * Function:    sendCommand
 * Description: send an command package
 * Paramer:     QByteArray baCmdValue - the command data
 * Return:      int - the error code
 */
int SerialportPlugin::sendCommand(const QByteArray baInput, bool bQueued)
{
    qDebug() << Q_FUNC_INFO << baInput.size() << bQueued;

    int nRetVal = SerialportMgr::getInstance(this)->writeData(baInput, bQueued);

    qDebug() << Q_FUNC_INFO << nRetVal;
    return nRetVal;
}

/*
 * Function:    execCommand
 * Description: send an command package & receive data
 * Paramer:     QByteArray &baDataInOut - the buffer for send & receive data
 *              const int nSleep - Wait time before reading data
 * Return:      int - the error code
 */
int SerialportPlugin::execCommand(QByteArray &baDataInOut, const int nSleep)
{
    qDebug() << Q_FUNC_INFO << baDataInOut.size() << nSleep;

    int nRetVal;
    QByteArray baOutput;

    // Start fetch data
    m_bFetchData = true;

    nRetVal = SerialportMgr::getInstance(this)->fetchData(baOutput, baDataInOut, nSleep);
    baDataInOut.clear();
    if(0 < nRetVal) {
        baDataInOut = baOutput;
    }

    // Stop fetch data
    m_bFetchData = false;

    qDebug() << Q_FUNC_INFO << nRetVal;
    return nRetVal;
}

/*
 * Function:    slotDataReceived
 * Description: Received data from COM
 * Paramer:     const QByteArray &baData - the data buffered
 * Return:      void
 */
void SerialportPlugin::slotDataReceived(const QByteArray &baData)
{
    qDebug() << Q_FUNC_INFO << baData.size();

#ifndef QT_PROJECT_T1
    // Fetch a payload packet from serialport
    emit signalPayloadReceived(0, baData);
    return;
#endif

    // parse the received package
    int nRetVal = this->_parseRcvPkg(baData, m_bFetchData);

    qDebug() << Q_FUNC_INFO << nRetVal;
}

/*
* Function:		slotBuildACK
* Description:	build & send an ACK command
* Paramer:		const ST_MIN_PKG_INFO &stPkgInfo - the structure of "ST_MIN_PKG_INFO"
* Return:		void
*/
void SerialportPlugin::slotBuildACK(const ST_MIN_PKG_INFO &stPkgInfo)
{
    qDebug() << Q_FUNC_INFO << stPkgInfo.ucVersion << stPkgInfo.ucMachineID << stPkgInfo.ucCommandID << stPkgInfo.ucTargetID << stPkgInfo.ucSourceID << stPkgInfo.usSequence;

    // Send an ACK command
    unsigned char ucMachineID = 0x0;
    unsigned short usDataLength = 0x0000;
    unsigned short usPkgCRC = 0x0000;
    unsigned char unHigh = 0x0;
    unsigned char unLow = 0x0;

    QByteArray baPkgInfo;
    baPkgInfo.push_back(stPkgInfo.ucHeader1);
    baPkgInfo.push_back(stPkgInfo.ucHeader2);
    baPkgInfo.push_back(stPkgInfo.ucHeader3);
    baPkgInfo.push_back(stPkgInfo.ucHeader4);
    baPkgInfo.push_back(stPkgInfo.ucVersion);
    baPkgInfo.push_back(ucMachineID);
    baPkgInfo.push_back(E_PKG_CT_ACK);
    baPkgInfo.push_back(stPkgInfo.ucSourceID);
    baPkgInfo.push_back(stPkgInfo.ucTargetID);
    baPkgInfo.push_back(E_PKG_FMT_JSON);

    // set the sequence
    unHigh = (stPkgInfo.usSequence >> 8) & 0xff;
    unLow = stPkgInfo.usSequence & 0xff;
    baPkgInfo.push_back(unLow);
    baPkgInfo.push_back(unHigh);

    // set the reserved data
    unHigh = (stPkgInfo.usReserved >> 8) & 0xff;
    unLow = stPkgInfo.usReserved & 0xff;
    baPkgInfo.push_back(unLow);
    baPkgInfo.push_back(unHigh);

    // set the data length
    unHigh = (usDataLength >> 8) & 0xff;
    unLow = usDataLength & 0xff;
    baPkgInfo.push_back(unLow);
    baPkgInfo.push_back(unHigh);

    // get CRC value
    QByteArray baCRC = baPkgInfo;
    usPkgCRC = GetCRC16((const unsigned char *)baCRC.data(), baCRC.size(), 0);

    // set CRC value
    unHigh = (usPkgCRC >> 8) & 0xff;
    unLow = usPkgCRC & 0xff;
    baPkgInfo.push_back(unHigh);
    baPkgInfo.push_back(unLow);

    qDebug() << Q_FUNC_INFO << baPkgInfo.size() << baPkgInfo.toHex(' ');

    // replay an ack comand to COM
    emit signalReplyACK(baPkgInfo, true);

    qDebug() << Q_FUNC_INFO;
}

/*
 * Function:    _parseRcvPkg
 * Description: parse the received package
 * Paramer:     const QByteArray &baPkgInfo - the data package
 *              bool bFetchData - Is Fetch data by execCommand function
 * Return:      int, 0: success; otherwise: error code
 */
int SerialportPlugin::_parseRcvPkg(const QByteArray &baPkgInfo, bool bFetchData)
{
    qDebug() << Q_FUNC_INFO << baPkgInfo.size() << baPkgInfo.toHex(' ') << baPkgInfo << bFetchData;

    int nRtnVal = 0;

    ST_MIN_PKG_INFO stPkgInfo;
    QDataStream dsData(baPkgInfo);
    int nPkgLen = baPkgInfo.size();

    if(DEF_MIN_PKG_SIZE > nPkgLen) {
        nRtnVal = -1;
        goto do_exit;
    }

    dsData.setByteOrder(QDataStream::LittleEndian);

    // read data byte by byte
    dsData >> stPkgInfo.ucHeader1 >> stPkgInfo.ucHeader2 >> stPkgInfo.ucHeader3 >> stPkgInfo.ucHeader4 >> stPkgInfo.ucVersion
            >> stPkgInfo.ucMachineID >> stPkgInfo.ucCommandID >> stPkgInfo.ucTargetID >> stPkgInfo.ucSourceID
            >> stPkgInfo.ucDataFormat >> stPkgInfo.usSequence >> stPkgInfo.usReserved >> stPkgInfo.usDataLength;

    qDebug() << Q_FUNC_INFO << stPkgInfo.ucHeader1 << stPkgInfo.ucHeader2 << stPkgInfo.ucHeader3 << stPkgInfo.ucHeader4 << stPkgInfo.ucVersion
             << stPkgInfo.ucMachineID << stPkgInfo.ucCommandID << stPkgInfo.ucTargetID << stPkgInfo.ucSourceID
             << stPkgInfo.ucDataFormat << stPkgInfo.usSequence << stPkgInfo.usReserved << stPkgInfo.usDataLength;

    if((DEF_PKG_HEADER1 != stPkgInfo.ucHeader1) || (DEF_PKG_HEADER2 != stPkgInfo.ucHeader2)
            || (DEF_PKG_HEADER3 != stPkgInfo.ucHeader3) || (DEF_PKG_HEADER4 != stPkgInfo.ucHeader4)) {
        nRtnVal = -2;
        goto do_exit;
    }

    switch (stPkgInfo.ucCommandID) {
    case E_PKG_CT_ACK:
        // ack to IAP
        qDebug() << Q_FUNC_INFO << "E_PKG_CT_ACK";
        break;
    case E_PKG_CT_DATA:
    case E_PKG_CT_REPLY:
        // reply from IAP
        if(E_PKG_FMT_JSON == stPkgInfo.ucDataFormat) {
            if(DEF_PKG_MIN_SIZE == (nPkgLen - stPkgInfo.usDataLength)) {
                // build & send an ack command
                emit signalBuildACK(stPkgInfo);

                QByteArray baPayload;
                baPayload.resize(stPkgInfo.usDataLength);
                dsData.readRawData(baPayload.data(), stPkgInfo.usDataLength);
                qDebug() << Q_FUNC_INFO << stPkgInfo.usDataLength << baPayload.toHex(' ') << baPayload;

                if(bFetchData) {
                    if(baPayload.contains(DF_NOTIFY_HEARTBEAT) || baPayload.contains(DF_NOTIFY_FLUOR_DATA)) {
                        // Receive a payload packet from serialport
                        emit signalPayloadReceived(stPkgInfo.ucCommandID, baPayload);
                    }
                    else {
                        // Fetch a payload packet from serialport
                        emit signalPayloadFetched(baPayload);
                    }
                }
                else {
                    // Receive a payload packet from serialport
                    emit signalPayloadReceived(stPkgInfo.ucCommandID, baPayload);
                }
            }
        }
        break;
    case E_PKG_CT_NOTIFY:
        // notify from IAP
        if(E_PKG_FMT_JSON == stPkgInfo.ucDataFormat) {
            if(DEF_PKG_MIN_SIZE == (nPkgLen - stPkgInfo.usDataLength)) {
                // build & send an ack command
                emit signalBuildACK(stPkgInfo);

                QByteArray baPayload;
                baPayload.resize(stPkgInfo.usDataLength);
                dsData.readRawData(baPayload.data(), stPkgInfo.usDataLength);
                qDebug() << Q_FUNC_INFO << stPkgInfo.usDataLength << baPayload.toHex(' ') << baPayload;

                // receive a payload packet from serialport
                emit signalPayloadReceived(stPkgInfo.ucCommandID, baPayload);
            }
        }
        break;
    default:
        qDebug() << Q_FUNC_INFO << "UNKOWN";
        break;
    }

do_exit:
    qDebug() << Q_FUNC_INFO << nRtnVal << stPkgInfo.ucCommandID;
    return nRtnVal;
}
